package io.netty.example.niodemo;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


/**
 * @description： NioServer
 * @Author MRyan
 * @Date 2023/1/27 21:29
 * @Version 1.0
 */
public class NioServer {

    private ServerSocketChannel serverSocketChannel;
    private Selector selector;


    public static void main(String[] args) throws IOException {
        NioServer server = new NioServer();
    }


    public NioServer() throws IOException {
        // 打开 Server Socket Channel
        serverSocketChannel = ServerSocketChannel.open();
        // 配置为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 绑定 Server port
        serverSocketChannel.socket().bind(new InetSocketAddress(8081));
        // 创建 Selector
        selector = Selector.open();
        // 注册 Server Socket Channel 到 Selector
        // 将 serverSocketChannel 到 selector 中，并对 SelectionKey.OP_ACCEPT 事件感兴趣。这样子在客户端连接服务端时，我们就可以处理该 IO 事件。
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server 启动完成");
        // 基于 Selector 处理 IO 事件
        handleKeys();
    }

    @SuppressWarnings("Duplicates")
    private void handleKeys() throws IOException {
        // demo 不考虑服务端关闭的逻辑
        while (true) {
            // 通过 Selector 选择 Channel
            // 每 10 秒阻塞等待有就绪的 IO 事件
            int selectNums = selector.select(10 * 1000L);
            // 当不存在就绪的 IO 事件，直接 continue ，继续下一次阻塞等待
            if (selectNums == 0) {
                continue;
            }
            System.out.println("选择 Channel 数量：" + selectNums);

            // 遍历可选择的 Channel 的 SelectionKey 集合
            // 获得有就绪的 IO 事件( 也可以称为“选择的” ) Channel 对应的 SelectionKey 集合。
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 移除下面要处理的 SelectionKey
                iterator.remove();
                // 忽略无效的 SelectionKey
                if (!key.isValid()) {
                    continue;
                }
                // 处理 key
                handleKey(key);
            }
        }
    }

    /**
     * 因为 SelectionKey 可以同时对一个 Channel 的多个事件感兴趣
     *
     * @param key
     * @throws IOException
     */
    private void handleKey(SelectionKey key) throws IOException {
        // Channel可读可写，放开注释展示客户端写能力
        // 接受连接就绪
        if (key.isAcceptable()) {
            handleAcceptableKey(key);
        }
        // 读就绪
        if (key.isReadable()) {
            handleReadableKey(key);
        }
       /* // 写就绪
        if (key.isWritable()) {
            handleWritableKey(key);
        }*/
    }

    private void handleAcceptableKey(SelectionKey key) throws IOException {
        // 接受 Client Socket Channel
        // 获得连接的客户端的 SocketChannel
        SocketChannel clientSocketChannel = ((ServerSocketChannel) key.channel()).accept();
        // 配置客户端的 SocketChannel 为非阻塞，否则无法使用 Selector
        clientSocketChannel.configureBlocking(false);
        // log
        System.out.println("接受新的 Channel");
        // 注册客户端的 SocketChannel 到 selector 中，并对 SelectionKey.OP_READ 事件感兴趣。这样，在客户端发送消息( 数据 )到服务端时，我们就可以处理该 IO 事件。
        clientSocketChannel.register(selector, SelectionKey.OP_READ, new ArrayList<String>());
    }

    private void handleReadableKey(SelectionKey key) throws IOException {
        // Client Socket Channel
        SocketChannel clientSocketChannel = (SocketChannel) key.channel();
        // 读取数据
        ByteBuffer readBuffer = CodecUtil.read(clientSocketChannel);
        // 处理连接已经断开的情况
        if (readBuffer == null) {
            System.out.println("断开 Channel");
            // 消注册 selector 对该客户端的 SocketChannel 的感兴趣的 IO 事件
            clientSocketChannel.register(selector, 0);
            return;
        }
        // 打印数据
        if (readBuffer.position() > 0) { // 写入模式下，
            String content = CodecUtil.newString(readBuffer);
            System.out.println("读取数据：" + content);

            // Channel可读可写，放开注释展示客户端读能力
       /*     // 添加到响应队列
            List<String> responseQueue = (ArrayList<String>) key.attachment();
            responseQueue.add("响应：" + content);
            // 注册 Client Socket Channel 到 Selector
            // 其中第三个参数，需要继续传入响应队列( responseQueue )，因为每次注册生成新的 SelectionKey 。若不传入，下面的 #handleWritableKey(SelectionKey key) 方法，会获得不到响应队列( responseQueue )。
            clientSocketChannel.register(selector, SelectionKey.OP_WRITE, key.attachment());*/
        }
    }

    @SuppressWarnings("Duplicates")
    private void handleWritableKey(SelectionKey key) throws ClosedChannelException {
        // Client Socket Channel
        SocketChannel clientSocketChannel = (SocketChannel) key.channel();

        // 遍历响应队列
        List<String> responseQueue = (ArrayList<String>) key.attachment();
        for (String content : responseQueue) {
            // 打印数据
            System.out.println("写入数据：" + content);
            // 返回
            CodecUtil.write(clientSocketChannel, content);
        }
        responseQueue.clear();
        // 再结束写入后，需要重新注册客户端的 SocketChannel 到 selector 中，并对 SelectionKey.OP_READ 事件感兴趣,因为如果不取消掉注册掉对 SelectionKey.OP_READ 事件感兴趣，就会导致反复触发无用的写事件处理
        // 注册 Client Socket Channel 到 Selector
        clientSocketChannel.register(selector, SelectionKey.OP_READ, responseQueue);
    }

}